// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System.IO;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
    public class Dfs
    {
        internal class CacheEntry
        {
            internal long Expiration;

            internal Hashtable Map;

            internal CacheEntry(long ttl)
            {
                if (ttl == 0)
                {
                    ttl = Ttl;
                }
                Expiration = Runtime.CurrentTimeMillis() + ttl * 1000L;
                Map = new Hashtable();
            }
        }

        internal static LogStream Log = LogStream.GetInstance();

        internal static readonly bool StrictView
            = Config.GetBoolean("jcifs.smb.client.dfs.strictView", false);

        internal static readonly long Ttl
            = Config.GetLong("jcifs.smb.client.dfs.ttl", 300);

        internal static readonly bool Disabled
            = Config.GetBoolean("jcifs.smb.client.dfs.disabled", false);

        internal static CacheEntry FalseEntry = new CacheEntry(0L);

        internal CacheEntry Domains;

        internal CacheEntry Referrals;

        /// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
        public virtual Hashtable GetTrustedDomains(NtlmPasswordAuthentication auth)
        {
            if (Disabled || auth.Domain == "?")
            {
                return null;
            }
            if (Domains != null && Runtime.CurrentTimeMillis() > Domains.Expiration)
            {
                Domains = null;
            }
            if (Domains != null)
            {
                return Domains.Map;
            }
            try
            {
                UniAddress addr = UniAddress.GetByName(auth.Domain, true);
                SmbTransport trans = SmbTransport.GetSmbTransport(addr, 0);
                CacheEntry entry = new CacheEntry(Ttl * 10L);
                DfsReferral dr = trans.GetDfsReferrals(auth, string.Empty, 0);
                if (dr != null)
                {
                    DfsReferral start = dr;
                    do
                    {
                        string domain = dr.Server.ToLower();
                        entry.Map.Put(domain, new Hashtable());
                        dr = dr.Next;
                    }
                    while (dr != start);
                    Domains = entry;
                    return Domains.Map;
                }
            }
            catch (IOException ioe)
            {
                if (Log.Level >= 3)
                {
                    Runtime.PrintStackTrace(ioe, Log);
                }
                if (StrictView && ioe is SmbAuthException)
                {
                    throw (SmbAuthException)ioe;
                }
            }
            return null;
        }

        /// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
        public virtual bool IsTrustedDomain(string domain, NtlmPasswordAuthentication auth)
        {
            Hashtable domains = GetTrustedDomains(auth);
            if (domains == null)
            {
                return false;
            }
            domain = domain.ToLower();
            return domains.Get(domain) != null;
        }

        /// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
        public virtual SmbTransport GetDc(string domain, NtlmPasswordAuthentication auth)
        {
            if (Disabled)
            {
                return null;
            }
            try
            {
                UniAddress addr = UniAddress.GetByName(domain, true);
                SmbTransport trans = SmbTransport.GetSmbTransport(addr, 0);
                DfsReferral dr = trans.GetDfsReferrals(auth, "\\" + domain, 1);
                if (dr != null)
                {
                    DfsReferral start = dr;
                    IOException e = null;
                    do
                    {
                        try
                        {
                            addr = UniAddress.GetByName(dr.Server);
                            return SmbTransport.GetSmbTransport(addr, 0);
                        }
                        catch (IOException ioe)
                        {
                            e = ioe;
                        }
                        dr = dr.Next;
                    }
                    while (dr != start);
                    throw e;
                }
            }
            catch (IOException ioe)
            {
                if (Log.Level >= 3)
                {
                    Runtime.PrintStackTrace(ioe, Log);
                }
                if (StrictView && ioe is SmbAuthException)
                {
                    throw (SmbAuthException)ioe;
                }
            }
            return null;
        }

        /// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
        public virtual DfsReferral GetReferral(SmbTransport trans,
                                               string domain,
                                               string root,
                                               string path,
                                               NtlmPasswordAuthentication auth)
        {
            if (Disabled)
            {
                return null;
            }
            try
            {
                string p = "\\" + domain + "\\" + root;
                if (path != null)
                {
                    p += path;
                }
                DfsReferral dr = trans.GetDfsReferrals(auth, p, 0);
                if (dr != null)
                {
                    return dr;
                }
            }
            catch (IOException ioe)
            {
                if (Log.Level >= 4)
                {
                    Runtime.PrintStackTrace(ioe, Log);
                }
                if (StrictView && ioe is SmbAuthException)
                {
                    throw (SmbAuthException)ioe;
                }
            }
            return null;
        }

        /// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
        public virtual DfsReferral Resolve(string domain,
                                           string root,
                                           string path,
                                           NtlmPasswordAuthentication auth)
        {
            lock (this)
            {
                DfsReferral dr = null;
                long now = Runtime.CurrentTimeMillis();
                if (Disabled || root.Equals("IPC$"))
                {
                    return null;
                }
                Hashtable domains = GetTrustedDomains(auth);
                if (domains != null)
                {
                    domain = domain.ToLower();
                    Hashtable roots = (Hashtable)domains.Get(domain);
                    if (roots != null)
                    {
                        SmbTransport trans = null;
                        root = root.ToLower();
                        CacheEntry links = (CacheEntry)roots.Get(root);
                        if (links != null && now > links.Expiration)
                        {
                            //Sharpen.Collections.Remove(roots, root);
                            roots.Remove(root);
                            links = null;
                        }
                        if (links == null)
                        {
                            if ((trans = GetDc(domain, auth)) == null)
                            {
                                return null;
                            }
                            dr = GetReferral(trans, domain, root, path, auth);
                            if (dr != null)
                            {
                                int len = 1 + domain.Length + 1 + root.Length;
                                links = new CacheEntry(0L);
                                DfsReferral tmp = dr;
                                do
                                {
                                    if (path == null)
                                    {
                                        // TODO: fix this
                                        //tmp.map = links.map;
                                        tmp.Key = "\\";
                                    }
                                    tmp.PathConsumed -= len;
                                    tmp = tmp.Next;
                                }
                                while (tmp != dr);
                                if (dr.Key != null)
                                {
                                    links.Map.Put(dr.Key, dr);
                                }
                                roots.Put(root, links);
                            }
                            else
                            {
                                if (path == null)
                                {
                                    roots.Put(root, FalseEntry);
                                }
                            }
                        }
                        else
                        {
                            if (links == FalseEntry)
                            {
                                links = null;
                            }
                        }
                        if (links != null)
                        {
                            string link = "\\";
                            dr = (DfsReferral)links.Map.Get(link);
                            if (dr != null && now > dr.Expiration)
                            {
                                //Sharpen.Collections.Remove(links.map, link);
                                links.Map.Remove(link);
                                dr = null;
                            }
                            if (dr == null)
                            {
                                if (trans == null)
                                {
                                    if ((trans = GetDc(domain, auth)) == null)
                                    {
                                        return null;
                                    }
                                }
                                dr = GetReferral(trans, domain, root, path, auth);
                                if (dr != null)
                                {
                                    dr.PathConsumed -= 1 + domain.Length + 1 + root.Length;
                                    dr.Link = link;
                                    links.Map.Put(link, dr);
                                }
                            }
                        }
                    }
                }
                if (dr == null && path != null)
                {
                    if (Referrals != null && now > Referrals.Expiration)
                    {
                        Referrals = null;
                    }
                    if (Referrals == null)
                    {
                        Referrals = new CacheEntry(0);
                    }
                    string key = "\\" + domain + "\\" + root;
                    if (path.Equals("\\") == false)
                    {
                        key += path;
                    }
                    key = key.ToLower();
                    //ListIterator<object> iter = new ListIterator<object>(referrals.map.Keys.GetEnumerator(), 0);
                    foreach (var current in Referrals.Map.Keys)
                    {
                        string _key = (string)current;
                        int klen = _key.Length;
                        bool match = false;
                        if (klen == key.Length)
                        {
                            match = _key.Equals(key);
                        }
                        else
                        {
                            if (klen < key.Length)
                            {
                                match = _key.RegionMatches(false, 0, key, 0, klen) && key[klen] == '\\';
                            }
                        }
                        if (match)
                        {
                            dr = (DfsReferral)Referrals.Map.Get(_key);
                        }
                    }
                }
                return dr;
            }
        }

        internal virtual void Insert(string path, DfsReferral dr)
        {
            lock (this)
            {
                int s1;
                int s2;
                string server;
                string share;
                string key;
                if (Disabled)
                {
                    return;
                }
                s1 = path.IndexOf('\\', 1);
                s2 = path.IndexOf('\\', s1 + 1);
                server = Runtime.Substring(path, 1, s1);
                share = Runtime.Substring(path, s1 + 1, s2);
                key = Runtime.Substring(path, 0, dr.PathConsumed).ToLower();
                int ki = key.Length;
                while (ki > 1 && key[ki - 1] == '\\')
                {
                    ki--;
                }
                if (ki < key.Length)
                {
                    key = Runtime.Substring(key, 0, ki);
                }
                dr.PathConsumed -= 1 + server.Length + 1 + share.Length;
                if (Referrals != null && (Runtime.CurrentTimeMillis() + 10000) > Referrals.Expiration)
                {
                    Referrals = null;
                }
                if (Referrals == null)
                {
                    Referrals = new CacheEntry(0);
                }
                Referrals.Map.Put(key, dr);
            }
        }
    }
}
